package abinly.harmony.permission.compiler;

import abinly.harmony.permission.NeedsCallingOrSelfPermission;
import abinly.harmony.permission.NeedsCallingPermission;
import abinly.harmony.permission.NeedsSelfPermission;
import abinly.harmony.permission.OnPermissionBlocked;
import com.squareup.javapoet.*;

import javax.annotation.processing.Filer;
import javax.lang.model.element.*;
import javax.lang.model.util.Elements;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.WeakHashMap;

public class CompilePermissionClassGenerator implements IPermissionClassGenerator {

    private static final String HELPER_CLASS_SUFFIX = "PermissionHelper";


    private final TypeElement mTypeElement;

    public CompilePermissionClassGenerator(TypeElement mTypeElement) throws IllegalArgumentException {
        this.mTypeElement = mTypeElement;
    }

    @Override
    public void generateJavaCodeToFile(Elements elementUtils, Filer filer) throws IOException {

        //构建Class类文件
        String clazzName = mTypeElement.getSimpleName().toString() + HELPER_CLASS_SUFFIX;
        TypeSpec.Builder typeClassBuilder = TypeSpec.classBuilder(clazzName);
        typeClassBuilder.addModifiers(Modifier.PUBLIC, Modifier.FINAL);

        //添加构造方法
        MethodSpec constructorMethodSpec = MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build();
        typeClassBuilder.addMethod(constructorMethodSpec);

        //添加静态属性变量
        ClassName targetClassName = ClassName.get(WeakHashMap.class);
        ClassName keyClassName = ClassName.get(Integer.class);
        ClassName valueClassName = ClassName.get(mTypeElement);
        ParameterizedTypeName targetObjectMap = ParameterizedTypeName.get(targetClassName, keyClassName, valueClassName);
        FieldSpec fieldSpec = FieldSpec.builder(targetObjectMap, "TARGET_OBJECT_MAPS")
                .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
                .initializer("new $T()", WeakHashMap.class)
                .build();
        typeClassBuilder.addField(fieldSpec);

        MethodSpec.Builder callbackMethodSpecBuilder = MethodSpec.methodBuilder("onRequestPermissionsFromUserResult");
        callbackMethodSpecBuilder.addModifiers(Modifier.PUBLIC, Modifier.STATIC);
        callbackMethodSpecBuilder.addParameter(ParameterSpec.builder(int.class, "requestCode").build());
        callbackMethodSpecBuilder.addParameter(ParameterSpec.builder(String[].class, "permissions").build());
        callbackMethodSpecBuilder.addParameter(ParameterSpec.builder(int[].class, "grantResults").build());

        HashMap<String, PermissionGroupElement> groupElementHashMap = new HashMap<>();
        initPermissionGroupElementMap(elementUtils, groupElementHashMap);

        groupElementHashMap.forEach((key, permissionGroupElement) -> {

            IPermissionGroupGenerator generator = new CompilePermissionGroupGenerator(mTypeElement, permissionGroupElement);
            typeClassBuilder.addField(generator.generatePermissionFieldSpec());
            typeClassBuilder.addField(generator.generateRequestCodeFieldSpec());

            List<MethodSpec> methodSpecs = generator.generateRequestPermissionMethodSpec();
            methodSpecs.forEach(typeClassBuilder::addMethod);

            callbackMethodSpecBuilder.addCode(generator.generatePermissionResultCheckCodeBlock());

        });

        typeClassBuilder.addMethod(callbackMethodSpecBuilder.build());

        TypeSpec clazzNameSpec = typeClassBuilder.build();
        String packageName = elementUtils.getPackageOf(mTypeElement).asType().toString();

        JavaFile javaFile = JavaFile.builder(packageName, clazzNameSpec)
                .skipJavaLangImports(true).build();
        javaFile.writeTo(filer);
    }

    private void initPermissionGroupElementMap(Elements elementUtils, HashMap<String, PermissionGroupElement> hashMap) {
        List<? extends Element> allMemberElements = mTypeElement.getEnclosedElements();
        for (Element element : allMemberElements) {
            if (element instanceof ExecutableElement) {
                List<? extends AnnotationMirror> annotationMirrors = elementUtils.getAllAnnotationMirrors(element);
                for (AnnotationMirror annotationMirror : annotationMirrors) {
                    if (AnnotationChecker.isNeedsSelfPermissionAnnotation(annotationMirror)) {
                        NeedsSelfPermission needsSelfPermission = element.getAnnotation(NeedsSelfPermission.class);
                        String[] permissions = needsSelfPermission.value();
                        String elementKey = makeWeakHashKeyByPermissions(permissions);
                        PermissionGroupElement permissionGroupElement = hashMap.get(elementKey);
                        if (permissionGroupElement == null) {
                            permissionGroupElement = new PermissionGroupElement(permissions);
                            hashMap.put(elementKey, permissionGroupElement);
                        }
                        permissionGroupElement.setNeedsSelfPermissionElement((ExecutableElement) element);
                    } else if (AnnotationChecker.isNeedsCallingPermissionAnnotation(annotationMirror)) {
                        NeedsCallingPermission needsCallingPermission = element.getAnnotation(NeedsCallingPermission.class);
                        String[] permissions = needsCallingPermission.value();
                        String elementKey = makeWeakHashKeyByPermissions(permissions);
                        PermissionGroupElement permissionGroupElement = hashMap.get(elementKey);
                        if (permissionGroupElement == null) {
                            permissionGroupElement = new PermissionGroupElement(permissions);
                            hashMap.put(elementKey, permissionGroupElement);
                        }
                        permissionGroupElement.setNeedsCallingPermissionElement((ExecutableElement) element);
                    } else if (AnnotationChecker.isNeedsCallingOrSelfPermissionAnnotation(annotationMirror)) {
                        NeedsCallingOrSelfPermission needsCallingOrSelfPermission = element.getAnnotation(NeedsCallingOrSelfPermission.class);
                        String[] permissions = needsCallingOrSelfPermission.value();
                        String elementKey = makeWeakHashKeyByPermissions(permissions);
                        PermissionGroupElement permissionGroupElement = hashMap.get(elementKey);
                        if (permissionGroupElement == null) {
                            permissionGroupElement = new PermissionGroupElement(permissions);
                            hashMap.put(elementKey, permissionGroupElement);
                        }
                        permissionGroupElement.setNeedsCallingOrSelfPermissionElement((ExecutableElement) element);
                    } else if (AnnotationChecker.isOnPermissionBlockedAnnotation(annotationMirror)) {
                        OnPermissionBlocked permissionBlocked = element.getAnnotation(OnPermissionBlocked.class);
                        String[] permissions = permissionBlocked.value();
                        String elementKey = makeWeakHashKeyByPermissions(permissions);
                        PermissionGroupElement permissionGroupElement = hashMap.get(elementKey);
                        if (permissionGroupElement == null) {
                            permissionGroupElement = new PermissionGroupElement(permissions);
                            hashMap.put(elementKey, permissionGroupElement);
                        }
                        permissionGroupElement.setOnPermissionBlockedElement((ExecutableElement) element);
                    }
                }
            }

        }
    }

    private static String makeWeakHashKeyByPermissions(String... permissions) {
        return "WeakHashKey_" + Arrays.hashCode(permissions);
    }


}
